Introducing an extension to make pattern match works on modules

Hi all,
we are working on extension to make pattern match works on modules, feedback is welcome

Examples:

let {length, cons : con /* rename*/ } = module (List)
let test i = 
   let {length, v } = module (List)
  length i + v

It works similar to JS as below

let {length, cons} = require('list')

Use cases:

  • Reexports
    Imagine we reexport functions from module A, we can do it like this
let {fn1,fn2,fn3} = module(A)
  • Imports
    Imagine we want to import functions from module A*, we can do it like this
%%private(
let {fn1,fn2,fn3} = module (A1)
let {fn4,fn5,fn6} = module (A2) 
)

Note the private syntax is a bit ugly, but we are moving in the good direction

  • Local import to replace the usage of local open
    Previously user writes:
let calcuate = (x,var) => {
  open List 
  length x + var
}

This is bad for static analysis, since it is unclear var is from the argument or shadowed by module List,
Now you can write:

let calculate = (x,var) => {
   let { length } = List 
   length x + var  // var is clearly from the argument now
} 

Backwards compatibility:

  • No new syntax is introduced, the old type error is now a valid pattern, so it is backwards compatible

Drawbacks:

  • It is unclear how to import operators, this could be delayed
14 Likes

I love it. Have been wishing for “selective open” for a while now.

Great idea!

The parenthesized syntax (for instance let { (+) } = module(A)) is ambiguous in this context?

Seems like a nice feature. Am I right in thinking this is all at the syntax level?

I do wonder whether it’s worth thinking about the syntax type destructuring too (even if it’s not being implemented now) - since there may be a different syntax style that’ll work nicely for both. Just so there’s no backwards compatible issues later down the line.

Or maybe it’d be as simple as,

let { type t, someFn } = Module;

I like this idea, although I don’t like mixing let and type in the same expression, given that they’re different “layers” of the language. Perhaps they could be supported as separate expressions though?

let { a, b } = M
type { c, d<'e, 'f> } = M
module { G, H } = M

Come to think of it, “importing” modules this way would be very useful.

module { Array, Map, Option } = Belt
3 Likes

Another request would be to allow renaming in the destructuring expression:

let {fn: newNameForFn} = module(A)

Or

let {fn as newNameForFn} = module(A)

(first one seems more idiomatic to reason IMO)

1 Like

it requires a bit more work, since it is not a valid syntax yet

yes, we are using the first one

1 Like

Am I right in thinking this is all at the syntax level?

currently yes.

I do wonder whether it’s worth thinking about the syntax type destructuring too (even if it’s not being implemented now) - since there may be a different syntax style that’ll work nicely for both.

I agree, @johnj’s proposal makes more sense to me

1 Like

The pattern match over module for values is done on master.
We will evaluate how to apply pattern match over module for types, thanks for the feedback!

3 Likes

What is the syntax transform that is happening here? Would this just transform to a bunch of aliases or does it use FCM’s (given the module is packed using module(x)).

I’m curious if you imagine making that something like this eventually:

import {fn1,fn2,fn3} = module (A1)
import {fn4,fn5,fn6} = module (A2)

so that not re-exporting is low-friction? I assume having that be a low-friction thing to do would be desired in ML-ish languages as much as it is with others, so that names aren’t exported just because it’s the shortest way to write it?

1 Like

In OCaml 4.08+, there is a nice way of doing this. In ReScript terms, it would look like:

open {
  let {fn1,fn2,fn3} = module (A1)
  let {fn4,fn5,fn6} = module (A2)
}

ReScript is currently based on OCaml 4.06.

1 Like

I made a proposal here: https://github.com/rescript-lang/syntax/issues/302
so you can write

private let {fn1,fn2,fn3} = module (A1)
private let {fn3,fn4, fn5} = module (A2)
6 Likes

I like the idea, one of the first things my coworkers who write Haskell asked me when I started showing them Reason/ReScript was whether it can do import hiding. The new syntax is close enough, for functions anyway.

This would be awesome!

This is interesting. can this be done completely in syntax level?
I think we can parse:

module { Array, Map, Option } = Belt

as (aliases)

module Array = Belt.Array
module Map = Belt.Map
module Option = Belt.Option
1 Like

I like this:

We could even go further and do this:

module { 
  Array: { 
    let map: arrayMap, blit 
  }, 
  Map: { type t: map, let maxKey }, 
  Option: { let getWithDefault }
} = Belt
2 Likes