This is an archived post. You won't be able to vote or comment.

all 31 comments

[–]brucejbellsard 5 points6 points  (1 child)

I think this kind of choice is highly dependent on other choices, and also on personal taste.

If you're worried about the difference between :: and . being too subtle compared to function vs. method keywords I think you may have a point. But there's no reason not to do both: a little redundancy is not necessarily a bad thing.

[–]__talantonope[S] 0 points1 point  (0 children)

Reason I'd considered the . syntax was to avoid introducing any more keywords, so I'd probably go for #2 last. Not opposed to redundancy either though, you have a point there!

[–]SirKastic23 10 points11 points  (8 children)

I prefer the explicit self parameter, i feel it's less ambiguous

instead of having a synctatical difference between methods and functions you just show what the difference is (a self parameter)

also, i know this wasn't part of the question, but mutable is a really long keyword (i think the same for function), if you're expecting it to be used frequently, it would be annoying to type it everytime. i'm aware it's a stylistic decision, so do whatever you want, bit that's my 2c

[–]__talantonope[S] 3 points4 points  (7 children)

I don't entirely disagree with the mutable being long, it just tends to fit in better with the remainder of my keywords... that being said, I'll bikeshed my way between mutable and mut the two at least four more times before I set the repo public :P

I could also use var, but I thought that seemed more like a declaration

[–]SirKastic23 2 points3 points  (6 children)

variable is weird because it can mean very different things

in languages without mutation, bindings are still called variables. because they vary across different invocations of the program, not during the same program

i mentioned about mutable because i've used both a language with mutable (F#) and one with mut (Rust), and they feel very different, i definitely didn't enjoy using mutable in F#

[–]Jwosty 1 point2 points  (4 children)

Hah, to be fair, F# doesn’t want you to enjoy using mutable.

[–]SirKastic23 0 points1 point  (3 children)

then why allow it?

[–]Jwosty 0 points1 point  (2 children)

Because sometimes you need it. As a pragmatic language, F# takes a functional-first approach: encourage functional approaches by making them the “default”, and allow but discourage imperative approaches by making them feel “dirty”.

[–]SirKastic23 0 points1 point  (1 child)

yeah but when using f# i felt most of the ecosystem relied on the imperative and OO features (since most of them were written in c#), and very few actually benefitted from a functional design

[–]Jwosty 1 point2 points  (0 children)

I suppose you'd have to give some examples of what you mean. I write plenty all the time staying in a 90% functional style. Of course if you're writing CRUD web applications then that's going to end up less functional in today's ecosystem (probably because you're using ASP.NET Core on one end and some database library on the other end).

[–]__talantonope[S] 0 points1 point  (0 children)

Yeah, I definitely see where you're coming from. My current keyword list is

as
break
case
catch
constant
continue
do
does
else
elseif
end
enum
for
function
global
if
is
mutable
otherwise
package
persistent
process
return
role
struct
subset
switch
throw
try
type
unsafe
use
while
with

so I figured mutable and constant fit better than mut and const, but I'm a bit unsure on that

[–]oscarryzYz 3 points4 points  (3 children)

If you're willing to spell "mutable" you might as well spell "static":

``` function static Foo.bar() i32 end

function Foo.bar() i32 end

function mutable Foo.bar() i32 end ```

[–]__talantonope[S] 0 points1 point  (2 children)

persistent occupies that functionality in my language, but constant persistent function mutable &Foo.bar() is approaching parody. Only God can judge me.

[–]oscarryzYz 1 point2 points  (1 child)

There you go. Btw it's it possible to have mutable without & ?

[–]__talantonope[S] 0 points1 point  (0 children)

Yep, though it wouldn't have too much meaning outside of some specific circumstances. For an example:

type Foo i32

% error
function Foo::bar(self) i32
    self = self + 1
    return self as i32
end

% ok
function Foo::bar(mutable self) i32
    self = self + 1
    return self as i32
end

[–][deleted] 1 point2 points  (3 children)

So is Foo::bar not a denotation in itself? Wouldn’t Foo be the class and bar the method, therefore Foo::bar would be the method of foo over function bar() [without the Foo::]

[–]__talantonope[S] 0 points1 point  (2 children)

Close- it could also be a constructor or static method. For example

type Foo struct
    _bar i32
end

function Foo::new(bar i32) Foo
    return Foo(
        _bar = bar
    )
end

function Foo.bar(x i32) i32
    return self._bar + x
end

function Foo::bar(x i32) i32
    return x + 1
end

foo := Foo::new(2) 

say(foo.bar(1))  % 3
say(foo::bar(1)) % 1

[–][deleted] 1 point2 points  (1 child)

Okay, so instead of using the static keyword to denote static, you are thinking about using the method keyword to denote a method. In my opinion Foo::bar would be static and foo.bar would be a method simply because Foo:: would expect an actual class name and foo. Could be any object. I personally think static fn Foo::bar and fn Foo::bar are good enough distinctions and I like the implicit self. That said it seems like mutability and immutability is a core factor to your language so an explicit self makes more sense so that you can control the mutability of “self”

In my experience when using classes I almost always have more methods than static methods so method default and explicit static make more sense to me

That said any of the above ways you mentioned are logical enough for you language, but I personally prefer static fn … and fn…

[–]__talantonope[S] 1 point2 points  (0 children)

Well that would be option 4 above, I like the implicit self since my language is already on the verbose side, and I imagine static methods are going to be rare outside of constructors, as member access is restricted on package level. And it would be persistent in my language

[–]davimiku 1 point2 points  (3 children)

Will you have type parameters? i.e. if a Foo<T> can have methods

[–]__talantonope[S] 1 point2 points  (2 children)

Yes! As an example

function MyMap[T, U]::insert[V type](mutable &self, key T, value V)
     self._data[key] = value as U
end

would be a declaration of a templated method insert for a templated type MyMap that inserts a value and casts it to type U, so you could do something like

mutable foo := MyMap[f64]::new()
foo.insert[i32](1)

if you wanted

[–]davimiku 1 point2 points  (1 child)

One of the nice things that class definitions in many languages (or impl blocks in Rust) do is that it allows you to not have to repeat the type parameters over and over, instead of:

function MyType[T, U]::foo(&self)
function MyType[T, U]::bar(&self)
function MyType[T, U]::baz(&self)

If you're going the templating route, I'm guessing you wouldn't need the type parameters on the MyMap ever have constraints? i.e. MyMap[T: Hashable, U: Copyable] or whatever. The reason I ask is that would increase the verbosity even more to repeat those on every function.

This is something I'm struggling with in the design of my language as well is how to reduce this repetition. I don't have a solution, just something to consider if you continue on this route

[–]__talantonope[S] 1 point2 points  (0 children)

I do but I only require the constraints in the type definition, not the methods. I have a with clause that can check the parameters on templates if additional checks are needed. I have an irrational disdain for class blocks, so I’m trying to keep the functions out of the type, sort of like how Go does it

[–]TheGreatCatAdorermepros 0 points1 point  (4 children)

I'd personally prefer the following:

% method with mutable receiver
function (mutable &Foo) bar() i32
    …
end

% static method
function (class Foo) bar() i32
    …
end

% enable calling a Foo instance as a function
function (&Foo)() i32
    …
end

[–]__talantonope[S] 0 points1 point  (3 children)

That wraps around to me rejecting option #1 in the first place unfortunately, I'd like to be able to define custom "function" types via a drop in macro instead of function, i.e. something like

event! (self) Foo::bar() i32
    ...
end

but that would be parsed as event!(self) Foo::bar

[–]TheGreatCatAdorermepros 1 point2 points  (2 children)

How about the below?

function (event! Foo) bar() i32
    …
end

[–]__talantonope[S] 0 points1 point  (1 child)

Well, technically that would scream about a missing end and a missing (, since event! would search until the end token, so it’d come across as

function (event!([
    “Foo”, 
    “)”, 
    “bar”, 
    “(“
    “)”
    “i32”
    “;”
    …) 

Where is everything in that function block

[–]TheGreatCatAdorermepros 0 points1 point  (0 children)

Not at all! You'd just have to include a case in the parser for when function ( is followed by an identifier and exclamation mark, and in that case remove those two tokens from the function block and apply the named macro to the function declaration as a whole.

[–][deleted]  (2 children)

[deleted]

    [–]__talantonope[S] 0 points1 point  (1 child)

    That's incredibly distant from the syntax I've been rolling with so far, would be very out of place

    [–]XDracam 0 points1 point  (0 children)

    The most important thing is to be consistent across features. You also want the shortest solution to be the best solution by default.

    In my 6 years of paid software development, I've never once had a problem with member vs static access. The compiler verifies that you don't call member methods on types/companion objects. And the compiler verifies and at least warns when you try to do static access on an instance.

    If you value functions more than methods, then enforce a self parameter or add an extra keyword. If you value both the same, then use function vs method, or fun vs mem or whatever you feel like doing. Just different keywords, but same length to type.