From: Anthony Tavener <anthony.tavener@gmail.com>
To: Yaron Minsky <yminsky@janestreet.com>
Cc: "caml-list@inria.fr" <caml-list@inria.fr>
Subject: Re: [Caml-list] Heterogeneous dictionary
Date: Fri, 5 Apr 2013 13:55:23 -0600 [thread overview]
Message-ID: <CAN=ouMRtB_wOvxv66xoYT2eCpmjx1oGX8831qFusg+wZmxeP9Q@mail.gmail.com> (raw)
In-Reply-To: <CACLX4jSEm-T=QTB5Bbdkq--AEKdkTCaB2fduEJqpg=jkbiJwzg@mail.gmail.com>
[-- Attachment #1: Type: text/plain, Size: 13469 bytes --]
Hmm... that is some food for thought. I wasn't really considering
the creation of modules which might only hold a few keys.
Sometimes we overlook simple things. :) You've convinced me
enough to work with it and see how it fits -- it sounds like you've
had similar cases, and turning out "prettily" is appealing.
I do have a pattern of Module.t. I remember thinking that was
an odd idiom, at first, and used an "appropriate" full name. Once
I really started using modules, and especially functors, I saw the
light. Is there a design patterns for OCaml, somewhere? :)
Thank-you for wading through my walls of text and still offering
advice, Yaron!
On Fri, Apr 5, 2013 at 12:51 PM, Yaron Minsky <yminsky@janestreet.com>wrote:
> On Fri, Apr 5, 2013 at 2:27 PM, Anthony Tavener
> <anthony.tavener@gmail.com> wrote:
> >> Sure, but there's nothing that requires these to be stored together.
> >> They can be scattered to the four winds, and yet all used to keep
> >> things in the same Univ_map.t.
> >
> > I was working with a universal type, but that was exactly the
> > problem I encountered: I need to create the keys and hold them
> > somewhere. My original post might have been unclear, sorry. This is
> > what I meant by having to pre-create the inj/proj pair and house
> > them somewhere... where a single file would create a bottleneck of
> > types, or scattered becomes a "mess". Scattered would prove
> > confusing I think... with hundreds of keys that have no sensible
> > "home" module. Well, some might make sense, but most of these keys
> > are interstitial -- between modules, somewhat like messages. I was
> > trying to map out where various keys would live and it wasn't
> > obvious and would continue to be unclear when I wanted to use a
> > key. There would be cognitive burden of deciding home modules for
> > things which don't fit into the modular namespace.
>
> My experience with this kind of design is that this isn't that much of
> a problem. You can create multiple modules that house keys for
> different concerns. Indeed, in such designs I've historically created
> modules with typeful interfaces that back-end on such keys, but simply
> expose a typeful way of getting access to the properties in question,
> which I think turns out quite prettily.
>
> > So, a universal type with "keys" does solve the problem, for some
> > definition of solved. My concern is that the solution is too
> > cumbersome and confusing in this use-case. Very much like arguments
> > for polymorphic variants versus variants. Unfortunately I don't see
> > any way to "embed type" in a polymorphic variant (I did check
> > whether hashes could match between different function
> > implementations of same type-signature... which was kind of silly
> > because I'm pretty sure the function address would be used in the
> > hashvalue). Although I imagine types do have hashvalues at some
> > point in the compiler!
>
> Again, If you create wrapper modules for accessing and working with
> these properties, I think everything will work smoothly. Indeed, if
> you have a module T for a given type of thing to be stored in one of
> these maps, you can build your typeful accessors for T.t into module
> T. You just need to follow the design pattern of having one module
> per type you define.
>
> > Another (failed?) possibility I mentioned as a pipe-dream: if "keys"
> > could be left open "to trust", and then verified that all trusted
> > usages lined up at link-time. After you're first reply (Yaron), I
> > thought for a moment that monomorphic types might work like that --
> > resolved at link time. Of course, they're not, as a test quickly
> > confirmed. :( But... that's why I asked the wise body of OCamlers if
> > you have ideas -- I don't have a complete grasp of the language and
> > might be missing something or have a false assumption. It's a
> > complex type-system! I like it a lot though.
> >
> > Thinking more about this, and formulating these replies, I've
> > realized there's a common idiom in game development -- and maybe
> > software in general? It's use of string-hashes to create flexible
> > associations... from code to code, or code to data (I think that's
> > how it starts... you need to share "IDs" between code and data --
> > and then the technique spreads). Messages, names of special points
> > in artwork (eg "primary-grip"), effects, things accessible by
> > script... But with such extensive usage, you need tools to verify
> > correct usage as much as possible. C, of course, provides few
> > guarantees of anything, so you write your own tools. OCaml is
> > tantalizing with its rich typesystem -- and polymorphic variants are
> > like built-in string-hash support. So I kept trying to figure out a
> > way to get OCaml to do the heavy lifting... for idioms I might be
> > unreasonably stuck on. ;)
>
> Maybe. My intuition is that the design pattern you're talking about
> is a good one, and really can be made to work well in OCaml.
>
> y
>
> > On Fri, Apr 5, 2013 at 10:37 AM, Yaron Minsky <yminsky@janestreet.com>
> > wrote:
> >>
> >> On Thu, Apr 4, 2013 at 2:48 PM, Anthony Tavener
> >> <anthony.tavener@gmail.com> wrote:
> >> > The problem here...
> >> >
> >> > module Keys : sig
> >> > val recovery : int Univ_map.Key.t
> >> > val resist_pain : float Univ_map.Key.t
> >> > ...
> >> >
> >> > Would be that Keys would now have dependence on types spanning the
> >> > codebase.
> >>
> >> Sure, but there's nothing that requires these to be stored together.
> >> They can be scattered to the four winds, and yet all used to keep
> >> things in the same Univ_map.t.
> >>
> >> It's honestly very much like using a dynamic language, where you get
> >> to declare new items at will that can be cast in and out from a
> >> universal type.
> >>
> >> It's probably worth reading over the Univ module in Core as well,
> >> since Univ_map is built on it, and if Univ_map doesn't quite do what
> >> you want, you can probably built what you need precisely on top of
> >> Univ.
> >>
> >> > Say these modifiers use Wounds.t, Fatigue.t, Ability.Score.t, ...
> There
> >> > are
> >> > a
> >> > lot of types, which mostly have a limited scope (their own module and
> a
> >> > few
> >> > others). Wouldn't it be a problem to have all types brought into this
> >> > one
> >> > module, which every other module also becomes dependent on? Maybe it
> >> > just
> >> > feels like a problem but it's just an aesthetic -- Would you do this?
> A
> >> > few
> >> > hundred keys involving types from half the modules of the codebase?
> >> >
> >> >
> >> > I'm trying to use these "modifiers" for code organisation -- declaring
> >> > snippets of functionality (all of signature 'a -> 'a... to return
> >> > modified
> >> > input) in a lightweight manner, which can be applied elsewhere.
> >> >
> >> > For example, the Virtue module can add a lot of modifers to an entity
> >> > (UID).
> >> > Say one entity has virtues of Agility, Inspirational, and Sun-cyclic
> >> > Magic,
> >> > which each adding a few modifier functions keyed to various contexts
> >> > (sun-cyclic magic: "Casting" is +3 while the sun is up). The
> associated
> >> > modifiers would be picked up in code spread throughout the
> application.
> >> > Rules
> >> > for combat, spellcasting, even character dialog...
> >> >
> >> > Rather than having character dialog checking for "does he have this
> >> > virtue,
> >> > or
> >> > that one? How about this ability? Spell-effects? Reputations? ..." I
> >> > want to
> >> > apply all appropriate/active modifiers for entity and situation which
> >> > come
> >> > from other rules. So, dialog code might have a current_value, then...
> >> > eg.
> >> > let modified_value = apply_modifiers_for_context entity `CharmRoll
> >> > current_value
> >> >
> >> > There is a lot of work on my project which I've been avoiding because
> >> > the
> >> > direct approach is building hairy nests of checks which call out to
> code
> >> > everywhere... effectively splitting logic between the point of origin
> of
> >> > a
> >> > rule, and the point of application of it. I'd rather declare the
> >> > individual
> >> > rules, each in one piece, and "magically" apply the aggregate of
> rules.
> >> >
> >> >
> >> > In a way, what I'm looking for is having a grand-central lookup, which
> >> > *trusts* that I'm using the right keys at the right time (where the
> keys
> >> > are
> >> > purely symbolic with no type info)... but it would be nice if once the
> >> > whole
> >> > program is compiled it could identify whether that trust was broken
> >> > afterall.
> >> > A bit of a pipe-dream, and maybe even flawed logic. :)
> >> >
> >> > It might sound like I'm being too picky or even whiney... that
> declaring
> >> > keys
> >> > throughout the code is unacceptable, or having a file dependent on all
> >> > types
> >> > is problematic... maybe I am? The first seems disorganised and adds a
> >> > mental burden to deciding and knowing where keys live; the second is a
> >> > problem, isn't it?
> >> >
> >> > Dynamic languages can do what I want at the cost of typesafety. So I
> >> > might
> >> > just prefer to make that same tradeoff for one mechanism in my code...
> >> > "famous
> >> > last words"? I hope not. I hope it just works and I don't have
> >> > nightmares
> >> > about lurking segfaults. :)
> >> >
> >> >
> >> > On Thu, Apr 4, 2013 at 3:04 AM, David House <dhouse@janestreet.com>
> >> > wrote:
> >> >>
> >> >> I don't quite understand the problem. Here's an example of how one
> >> >> might use univ_map:
> >> >>
> >> >> open Core.Std
> >> >>
> >> >> module Keys : sig
> >> >> val recovery : int Univ_map.Key.t
> >> >> val resist_pain : float Univ_map.Key.t
> >> >> end = struct
> >> >> let recovery = Univ_key.Key.create "recovery" Int.sexp_of_t
> >> >> let resist_pain = Univ_key.Key.create "resist_pain" Float.sexp_of_t
> >> >> end
> >> >>
> >> >> (In practice this might be two files: keys.ml with the
> implementation
> >> >> and keys.mli with the signature.) You can then add things as follows:
> >> >>
> >> >> let add map ~key ~data = Univ_map.add_exn map key data in
> >> >> let map =
> >> >> Univ_map.empty
> >> >> |> add ~key:Keys.recovery ~data:4
> >> >> |> add ~key:Keys.resist_pain ~data:10.
> >> >> in
> >> >> ...
> >> >>
> >> >> On 4 April 2013 09:37, Anthony Tavener <anthony.tavener@gmail.com>
> >> >> wrote:
> >> >> > Thank-you for the advice and pointers, folks...
> >> >> >
> >> >> > Well, the common problem is still the same one I've been struggling
> >> >> > with:
> >> >> > "creating keys", and having to access them.
> >> >> >
> >> >> > I can't create keys "type-free" in a common module. As I figured...
> >> >> > having
> >> >> > "modifier.ml" with a bunch of Key.create will have monomorphic
> types
> >> >> > which
> >> >> > can't be resolved since with no usage in that module to make the
> type
> >> >> > concrete. I had a nagging feeling I'd need a "whole-program"
> >> >> > compiler...
> >> >> >
> >> >> > Instead I'd have to create keys in modules where they are used...
> but
> >> >> > then I
> >> >> > might have a mess of keys like Wounds.recovery, Combat.resist_pain,
> >> >> > ...
> >> >> > the
> >> >> > problem being that only a fraction of these keys actually make
> sense
> >> >> > being
> >> >> > associated to a particular module, and it gets confusing to know
> >> >> > which
> >> >> > (of
> >> >> > several candidates) I decided to stash them into. This was the
> >> >> > attraction to
> >> >> > polymorphic variants (which I rarely use) -- they give a
> pre-ordained
> >> >> > unique
> >> >> > ID based on a simple name... no declaration, and no module
> prefixing,
> >> >> > which
> >> >> > seems important to me for this case.
> >> >> >
> >> >> > Note that I have a "database" of tables with different types
> >> >> > (implemented by
> >> >> > first-class modules!), and it works great for the bulk of my
> >> >> > game-state,
> >> >> > but
> >> >> > each table is well-populated and heavily used in consistent manner.
> >> >> > These
> >> >> > modifiers though... they're a bit like ad-hoc message passing,
> where
> >> >> > I
> >> >> > can
> >> >> > submit any message and anywhere else add a snippet of code to
> >> >> > interpret
> >> >> > it
> >> >> > (not that I have any of that going on, otherwise it might hold the
> >> >> > solution!).
> >> >> >
> >> >> >
> >> >> >
> >> >> > On Thu, Apr 4, 2013 at 1:38 AM, Raphaël Proust <
> raphlalou@gmail.com>
> >> >> > wrote:
> >> >> >>
> >> >> >> On Thu, Apr 4, 2013 at 1:45 AM, Anthony Tavener
> >> >> >> <anthony.tavener@gmail.com> wrote:
> >> >> >> > […]
> >> >> >>
> >> >> >> And yet-another-solution, Ocsigen's Polytable:
> >> >> >> http://ocsigen.org/ocsigenserver/api/Polytables
> >> >> >>
> >> >> >>
> >> >> >> Cheers,
> >> >> >> --
> >> >> >> ______________
> >> >> >> Raphaël Proust
> >> >> >
> >> >> >
> >> >
> >> >
> >
> >
>
> --
> Caml-list mailing list. Subscription management and archives:
> https://sympa.inria.fr/sympa/arc/caml-list
> Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
> Bug reports: http://caml.inria.fr/bin/caml-bugs
>
[-- Attachment #2: Type: text/html, Size: 18259 bytes --]
next prev parent reply other threads:[~2013-04-05 19:55 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2013-04-04 0:45 Anthony Tavener
2013-04-04 1:29 ` Yaron Minsky
2013-04-04 2:18 ` Anthony Tavener
2013-04-04 6:19 ` Martin Jambon
2013-04-04 7:32 ` Alain Frisch
2013-04-04 18:16 ` Martin Jambon
2013-04-04 7:38 ` Raphaël Proust
2013-04-04 8:37 ` Anthony Tavener
2013-04-04 9:04 ` David House
2013-04-04 18:48 ` Anthony Tavener
2013-04-05 16:37 ` Yaron Minsky
2013-04-05 18:27 ` Anthony Tavener
2013-04-05 18:51 ` Yaron Minsky
2013-04-05 19:55 ` Anthony Tavener [this message]
2013-04-05 20:03 ` Yaron Minsky
2013-04-05 20:27 ` Anthony Tavener
2013-04-08 8:33 ` David House
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='CAN=ouMRtB_wOvxv66xoYT2eCpmjx1oGX8831qFusg+wZmxeP9Q@mail.gmail.com' \
--to=anthony.tavener@gmail.com \
--cc=caml-list@inria.fr \
--cc=yminsky@janestreet.com \
/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