[m-dev.] Suggested interface for sequence types in lib-v2
Ralph Becket
rbeck at microsoft.com
Sat Oct 21 01:26:17 AEDT 2000
>From Fergus Henderson on 20/10/2000 14:52:04
> You want
>
> :- func semidet_head(seq(T)) = T is semidet.
> ^^^^^^^^^^^
Doh.
>
> But see below about naming conventions.
>
> > :- func but_last(seq(T)) = seq(T).
> > :- func semidet_but_last(seq(T)) = seq(T) is semidet.
>
> What is that supposed to do?
These are supposed to return all but the last member of a
sequence, so if S is a non-empty sequence then
S = snoc(last(S), but_last(S))
> Is it supposed to return the second-last element in the sequence (if any)?
> If so, I think it would be better to name it `second_last'.
> But I don't think that it is useful enough to justify inclusion
> in the standard library.
The intended meaning is important for double-ended sequence
types. Providing naive default implementations should
relieve the library author of the burden of filling
everything in, if this is otherwise a problem.
> > :- pred project_member(seq(T)::in, T::out) is nondet.
>
> What is that supposed to do?
> How does it differ from `member'?
Several `solutions' style predicates expect the output argument
to be the last one. I included project_member/2 for this
reason. It's debatable whether or not it's useful enough to
warrant inclusion, but if we supply the obvious default
implementation based on member/2 then we should be okay.
> > :- func rev_append(seq(T), seq(T)) = seq(T).
>
> What is that supposed to do?
rev_append(S1, S2) = reverse(S1) ++ S2
The point is that there's often a more efficient implementation
of this operations than the RHS. e.g. for lists:
rev_append([], S2) = S2.
rev_append([S | S1], S2) = rev_append(S1, [S | S2]).
> > % XXX Is this really useful?
> > :- func condense_map(func(T1) = seq(T2), seq(T1)) = seq(T2).
>
> Is this just
>
> condense_map(F, S) = condense(map(F, S)).
>
> ?
> If so, I don't think it is worth including, unless there
> are some sequences for which condense_map(F, S) can be implemented
> more efficiently than condense(map(F, S)). Off-hand I don't know
> of any such sequences.
condense_map/2 is there to avoid construction of an intermediate
sequence.
This is part of a larger problem that e.g. the people at Mission
Critical have come across. In C++ one can typically traverse a large
library structure using iterators; in Mercury one usually has to
flatten such a thing into a sequence (list) and then traverse that.
If your structure is *very* large then this can be costly. While the
library has been extended to include more mapping operations etc., the
suite of such operations provided so far isn't comprehensive.
I wonder if this is justification for requiring provision of the means
to traverse structures lazily?
> > % REMOVING MEMBERS
> >
> > % Error unless S `contains` X.
> > :- func delete_first(T, seq(T)) = seq(T).
>
> I think that should be semidet.
> A det `delete_first_det' might be useful, but
> I think the semidet one is probably more useful.
Okay. The convention I was using was that operation foo would be
det while semidet_foo would be semidet. In this case it's not
clear whether the proper functionality is to delete the first
x if one is present, otherwise do nothing. Hmmm...
> > % INDEX BASED OPERATIONS
> > % index_in(S, head(S)) = 0.
> > :- func index_in(seq(T), T) = int. % Error unless S `contains` X.
> > :- func semidet_index_in(seq(T), T) = int is semidet.
>
> Our traditional naming scheme for these kind of things has been
> for the semidet one to have the unadorned name, and for the
> det one to get a `_det' suffix. I think this is a better idea,
> since the explicit `_det' suffix serves as a hint that the procedure
> might call error/1 in some circumstances, and so the programmer
> had better be careful to ensure that the parameters at each call
> meet the preconditions.
I think that is true for predicates, but that the reverse should
be the case for functions. After all, one usually expects a function
to be deterministic. I'm prepared to be converted on this one...
anybody else have any strong feelings?
> > :- func lookup(seq(T), int) = T. % Error unless 0 =< I <
length(S).
> > :- func seq(T) @ int = T. % Synonym for lookup/2.
> > :- func semidet_lookup(seq(T), int) = T.
>
> I'd rather semidet_lookup be named `search'.
> This is used very frequently.
Okay.
> > % replace(S, I, X) = take_n(I - 1, S) ++ cons(X, drop_up_to_n(I, S))
> > % Error unless 0 =< I < length(S).
> > %
> > :- func replace(seq(T), int, T) = seq(T).
> >
> > % change(S, I, F) = take_n(I - 1, S) ++ cons(F(lookup(S, I)),
drop_n(I,
> > S))
> > % Error unless 0 =< I < length(S).
> > %
> > :- func change(seq(T), int, func(T) = T) = seq(T).
>
> So is change(S, I, F) = replace(S, I, F(S `lookup` I))?
> If so, then (a) that would be a simpler way of documenting it
> and (b) is it really worth including in the standard library?
I've changed the comment to (a). The question here is
whether or not there are enough random-access sequence types for
which there are more efficient implementations than the RHS to
justify the inclusion of change/3.
The jury may still be out on this one, but for, say, bt_arrays and
maps change/3 would be handy and more efficient.
I've basically been following Okasaki's approach in that if a
class contains a large-enough subset of types for which there is
an efficient implementation of a reasonably common idiom then
that should go in the spec.
Again, I'm prepared to be convinced that I'm wrong.
Ralph
--
Ralph Becket | MSR Cambridge | rbeck at microsoft.com
--------------------------------------------------------------------------
mercury-developers mailing list
Post messages to: mercury-developers at cs.mu.oz.au
Administrative Queries: owner-mercury-developers at cs.mu.oz.au
Subscriptions: mercury-developers-request at cs.mu.oz.au
--------------------------------------------------------------------------
More information about the developers
mailing list